#include "vanila/baseobject.h"
#include "vanila/object.h"
#include "vanila/environment.h"
#include "vanila/allocator.h"
#include "vanila/object.h"

namespace vanila
{
static Allocator* allocator = utils::Singleton<Allocator>::instance();

//! \brief Construct a new Environment:: Environment object
//! \param[in] functionType funtion type
Environment::Environment(FunctionType functionType) : 
    _enclosing{nullptr},
    _function{nullptr}, 
    _functionType{functionType},
    _locals{}, 
    _upvalues{},
    _scopeDepth{0}
{
    this->_function = allocator->allocateFunction();
}

//! \brief add a local varibale's token
bool Environment::addLocal(const Token& name)
{
    if (this->_locals.size() == UINT8_COUNT)
        return false;

    this->_locals.push_back(Local{name, -1});
    return true;
}

//! \brief find the position of sepecify token name
int Environment::resolveLocal(const Token& name)
{
    for (int i = this->_locals.size() - 1; i >= 0; i--)
    {
        if (Token::identifiersEqual(this->_locals[i].name, name))
        {
            if (this->_locals[i].depth == -1)
                return RESOLVE_ERROR;
            return i;
        }
    }

    return -1;
}

//! \brief find the upvalue's position
int Environment::resolveUpvalue(const Token& name)
{
    // not higher enviroment!
    if (this->_enclosing == nullptr)
        return -1;

    // find the name in enclosing 
    int local = this->_enclosing->resolveLocal(name);
    if (local != -1)
    {
        // mark the local variable is been capture!
        this->_enclosing->locals()[local].isCaptured = true;
        return this->addUpvalue(static_cast<uint8_t>(local), true);
    }

    // find the higher environment
    int upvalue = this->_enclosing->resolveUpvalue(name);
    if (upvalue != -1)
        return this->addUpvalue(static_cast<uint8_t>(upvalue), false);

    // still no found, return -1, mean the name is a global variable
    return -1;
}

//! \brief mark the last local variable already been init
void Environment::markInitialized()
{
    if (this->_scopeDepth == 0)
        return;
    this->_locals.back().depth = this->_scopeDepth;
}

//! \brief add a upvalue in vector, and return its index
//! \param[in] index the upvalue's index in higher environment's local variables
//! \param[in] isLocal is this upvalue in enclosure or higher environment
int Environment::addUpvalue(uint8_t index, bool isLocal)
{
    uint32_t upvalueCount = this->_function->upvalueCount();

    // is the upvalue already exit?
    for (uint32_t i = 0; i < upvalueCount; i++)
    {
        Upvalue* upvalue = &(this->_upvalues[i]);
        if (upvalue->index == index && upvalue->isLocal == isLocal)
            return i;
    }

    // too much upvalue
    if (upvalueCount == UINT8_COUNT)
        return RESOLVE_ERROR;

    this->_upvalues.push_back(Upvalue{index, isLocal});
    this->_function->increaseUpvalueCount();
    return upvalueCount;
}

}